package com.concurrent.sample.common.util;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.boot.json.JsonParseException;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.util.StringUtils;

/**
 * JSON 工具类
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonUtils {

    public static final JsonMapper mapper;

    static {
        mapper = JsonMapper.builder()
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
            .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
            .build();
        mapper.registerModule(new JavaTimeModule()).registerModule(new ParameterNamesModule());
        mapper.getSerializerProvider().setNullValueSerializer(new MapNullValueSerializer());
    }

    public static class SerializationException extends RuntimeException {
        SerializationException(String message) {
            super(message);
        }

        SerializationException(Exception e) {
            super(e);
        }
    }


    private static final TypeReference<HashMap<String, Object>> typeRef
            = new TypeReference<HashMap<String, Object>>() {
    };

    private static final String JSON_DECODE_ERROR = "Failed to decode: [%s]";

    static class MapNullValueSerializer extends StdSerializer<Object> {
        public MapNullValueSerializer() {
            this(null);
        }

        public MapNullValueSerializer(Class<Object> t) {
            super(t);
        }

        @Override
        public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            gen.writeNull();
        }
    }

    public static String objectToJsonString(Object o) {
        String str;
        try {
            str = mapper.writeValueAsString(o);
        } catch (Exception e) {
            throw new JsonParseException(e);
        }
        return str;
    }

    public static Map<String, Object> mapFromJsonString(String context) {
        Map<String, Object> resultMap = null;
        if (context != null) {
            try {
                resultMap = mapper.readValue(context, typeRef);
            } catch (Exception e) {
                throw new JsonParseException(e);
            }
        }
        return resultMap;
    }


    public static String toJson(Object o) throws SerializationException {
        try {
            return mapper.writeValueAsString(o);
        } catch (JsonProcessingException e) {
            throw new SerializationException(e);
        }
    }

    private static JavaType getMapType(Class<?>... elementClasses) {
        return mapper.getTypeFactory().constructParametricType(HashMap.class, elementClasses);
    }

    public static Map<String, Object> fromJson(String context) throws SerializationException {
        Map<String, Object> resultMap = null;
        if (StringUtils.hasLength(context)) {
            JavaType type = getMapType(String.class, Object.class);
            try {
                resultMap = mapper.readValue(context, type);
            } catch (IOException e) {
                throw new SerializationException(String.format(JSON_DECODE_ERROR, context));
            }
        }
        return resultMap;
    }

    public static <T> T fromJson(Class<T> clazz, String context) throws SerializationException {
        try {
            return mapper.readValue(context, clazz);
        } catch (IOException e) {
            throw new SerializationException(String.format(JSON_DECODE_ERROR, context));
        }
    }


    public static <T> List<T> json2List(String jsonData, Class<T> beanType) {
        JavaType javaType = mapper.getTypeFactory().constructParametricType(List.class, beanType);

        try {
            return mapper.readValue(jsonData, javaType);
        } catch (Exception e) {
            throw new SerializationException(String.format(JSON_DECODE_ERROR, jsonData));
        }
    }

    public static <T> Set<T> json2Set(String jsonData, Class<T> beanType) {
        JavaType javaType = mapper.getTypeFactory().constructParametricType(Set.class, beanType);

        try {
            return mapper.readValue(jsonData, javaType);
        } catch (Exception e) {
            throw new SerializationException(String.format(JSON_DECODE_ERROR, jsonData));
        }
    }

    public static <K, V> Map<K, V> json2Map(String jsonData, Class<K> keyType, Class<V> valueType) {
        JavaType javaType = mapper.getTypeFactory().constructMapType(Map.class, keyType, valueType);

        try {
            return mapper.readValue(jsonData, javaType);
        } catch (Exception e) {
            throw new SerializationException(String.format(JSON_DECODE_ERROR, jsonData));
        }
    }
}
